package ca

import (
	"bytes"
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"errors"
	"fmt"
	"math/big"
	"os"
	"strings"
	"testing"
	"time"

	ct "github.com/google/certificate-transparency-go"
	cttls "github.com/google/certificate-transparency-go/tls"
	ctx509 "github.com/google/certificate-transparency-go/x509"
	"github.com/jmhodges/clock"
	"github.com/miekg/pkcs11"
	"github.com/prometheus/client_golang/prometheus"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/types/known/emptypb"

	capb "github.com/letsencrypt/boulder/ca/proto"
	"github.com/letsencrypt/boulder/config"
	"github.com/letsencrypt/boulder/core"
	corepb "github.com/letsencrypt/boulder/core/proto"
	berrors "github.com/letsencrypt/boulder/errors"
	"github.com/letsencrypt/boulder/features"
	"github.com/letsencrypt/boulder/goodkey"
	"github.com/letsencrypt/boulder/identifier"
	"github.com/letsencrypt/boulder/issuance"
	blog "github.com/letsencrypt/boulder/log"
	"github.com/letsencrypt/boulder/must"
	"github.com/letsencrypt/boulder/policy"
	rapb "github.com/letsencrypt/boulder/ra/proto"
	sapb "github.com/letsencrypt/boulder/sa/proto"
	"github.com/letsencrypt/boulder/test"
)

var (
	// * Random public key
	// * CN = not-example.com
	// * DNSNames = not-example.com, www.not-example.com
	CNandSANCSR = mustRead("./testdata/cn_and_san.der.csr")

	// CSR generated by Go:
	// * Random public key
	// * CN = not-example.com
	// * Includes an extensionRequest attribute for a well-formed TLS Feature extension
	MustStapleCSR = mustRead("./testdata/must_staple.der.csr")

	// CSR generated by Go:
	// * Random public key
	// * CN = not-example.com
	// * Includes an extensionRequest attribute for an unknown extension with an
	//   empty value. That extension's OID, 2.25.123456789, is on the UUID arc.
	//   It isn't a real randomly-generated UUID because Go represents the
	//   components of the OID as 32-bit integers, which aren't large enough to
	//   hold a real 128-bit UUID; this doesn't matter as far as what we're
	//   testing here is concerned.
	UnsupportedExtensionCSR = mustRead("./testdata/unsupported_extension.der.csr")

	// CSR generated by Go:
	// * Random public key
	// * CN = not-example.com
	// * Includes an extensionRequest attribute for the CT poison extension
	//   with a valid NULL value.
	CTPoisonExtensionCSR = mustRead("./testdata/ct_poison_extension.der.csr")

	// CSR generated by Go:
	// * Random public key
	// * CN = not-example.com
	// * Includes an extensionRequest attribute for the CT poison extension
	//   with an invalid empty value.
	CTPoisonExtensionEmptyCSR = mustRead("./testdata/ct_poison_extension_empty.der.csr")

	// CSR generated by Go:
	// * Random ECDSA public key.
	// * CN = [none]
	// * DNSNames = example.com, example2.com
	ECDSACSR = mustRead("./testdata/ecdsa.der.csr")

	// OIDExtensionCTPoison is defined in RFC 6962 s3.1.
	OIDExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3}

	// OIDExtensionSCTList is defined in RFC 6962 s3.3.
	OIDExtensionSCTList = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2}
)

func mustRead(path string) []byte {
	return must.Do(os.ReadFile(path))
}

// caArgs is a container for all of the arguments to
// NewCertificateAuthorityImpl. It exists so that tests can easily build a
// default certificateAuthorityImpl, but can also easily customize that object
// to exercise various behaviors. The expected usage flow is:
//
//	cargs := newTestCA(t)
//	cargs.foo = someOverride
//	ca := cargs.make()
//
// Its fields should remain identical to the NewCertificateAuthorityImpl args.
type caArgs struct {
	sa           sapb.StorageAuthorityCertificateClient
	sctService   rapb.SCTProviderClient
	pa           core.PolicyAuthority
	issuers      []*issuance.Issuer
	profiles     map[string]*issuance.Profile
	serialPrefix byte
	maxNames     int
	keyPolicy    goodkey.KeyPolicy
	logger       *blog.Mock
	metrics      *caMetrics
	clk          clock.FakeClock
}

// newCAArgs returns a caArgs populated with reasonable default values for testing.
func newCAArgs(t *testing.T) *caArgs {
	features.Reset()

	fc := clock.NewFake()
	fc.Add(1 * time.Hour)

	pa, err := policy.New(map[identifier.IdentifierType]bool{"dns": true}, nil, blog.NewMock())
	test.AssertNotError(t, err, "Couldn't create PA")
	err = pa.LoadIdentPolicyFile("../test/ident-policy.yaml")
	test.AssertNotError(t, err, "Couldn't set identifier policy")

	legacy, err := issuance.NewProfile(issuance.ProfileConfig{
		MaxValidityPeriod:   config.Duration{Duration: time.Hour * 24 * 90},
		MaxValidityBackdate: config.Duration{Duration: time.Hour},
		IgnoredLints:        []string{"w_subject_common_name_included"},
	})
	test.AssertNotError(t, err, "Loading test profile")
	modern, err := issuance.NewProfile(issuance.ProfileConfig{
		OmitCommonName:      true,
		OmitKeyEncipherment: true,
		OmitClientAuth:      true,
		OmitSKID:            true,
		MaxValidityPeriod:   config.Duration{Duration: time.Hour * 24 * 6},
		MaxValidityBackdate: config.Duration{Duration: time.Hour},
		IgnoredLints:        []string{"w_ext_subject_key_identifier_missing_sub_cert"},
	})
	test.AssertNotError(t, err, "Loading test profile")
	profiles := map[string]*issuance.Profile{
		"legacy": legacy,
		"modern": modern,
	}

	issuers := make([]*issuance.Issuer, 4)
	for i, name := range []string{"int-r3", "int-r4", "int-e1", "int-e2"} {
		issuers[i], err = issuance.LoadIssuer(issuance.IssuerConfig{
			Active:     true,
			IssuerURL:  fmt.Sprintf("http://not-example.com/i/%s", name),
			CRLURLBase: fmt.Sprintf("http://not-example.com/c/%s/", name),
			CRLShards:  10,
			Location: issuance.IssuerLoc{
				File:     fmt.Sprintf("../test/hierarchy/%s.key.pem", name),
				CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name),
			},
			Profiles: []string{"legacy", "modern"},
		}, fc)
		test.AssertNotError(t, err, "Couldn't load test issuer")
	}

	keyPolicy, err := goodkey.NewPolicy(nil, nil)
	test.AssertNotError(t, err, "Failed to create test keypolicy")

	signatureCount := prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "signatures",
			Help: "Number of signatures",
		},
		[]string{"purpose", "issuer"})
	signErrorCount := prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "signature_errors",
		Help: "A counter of signature errors labelled by error type",
	}, []string{"type"})
	lintErrorCount := prometheus.NewCounter(
		prometheus.CounterOpts{
			Name: "lint_errors",
			Help: "Number of issuances that were halted by linting errors",
		})
	certificatesCount := prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "certificates",
			Help: "Number of certificates issued",
		}, []string{"profile"})
	cametrics := &caMetrics{signatureCount, signErrorCount, lintErrorCount, certificatesCount}

	return &caArgs{
		sa:           &mockSA{},
		sctService:   &mockSCTService{},
		pa:           pa,
		issuers:      issuers,
		profiles:     profiles,
		serialPrefix: 0x11,
		maxNames:     2,
		keyPolicy:    keyPolicy,
		logger:       blog.NewMock(),
		metrics:      cametrics,
		clk:          fc,
	}
}

// make passes all of the caArgs' fields to the NewCertificateAuthorityImpl
// constructor and returns the result.
func (c *caArgs) make() (*certificateAuthorityImpl, error) {
	return NewCertificateAuthorityImpl(
		c.sa, c.sctService, c.pa, c.issuers, c.profiles, c.serialPrefix,
		c.maxNames, c.keyPolicy, c.logger, c.metrics, c.clk)
}

type mockSA struct{}

func (m *mockSA) AddSerial(ctx context.Context, req *sapb.AddSerialRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

func (m *mockSA) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

func (m *mockSA) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	return &emptypb.Empty{}, nil
}

func (m *mockSA) GetCertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
	return nil, berrors.NotFoundError("cannot find the cert")
}

func (m *mockSA) GetLintPrecertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
	return nil, berrors.NotFoundError("cannot find the precert")
}

type mockSCTService struct{}

func (m mockSCTService) GetSCTs(ctx context.Context, sctRequest *rapb.SCTRequest, _ ...grpc.CallOption) (*rapb.SCTResponse, error) {
	sct := ct.SignedCertificateTimestamp{
		SCTVersion: 0,
		Timestamp:  2020,
		Signature: ct.DigitallySigned{
			Signature: []byte{0},
		},
	}

	sctBytes, err := cttls.Marshal(sct)
	if err != nil {
		return nil, err
	}

	return &rapb.SCTResponse{SctDER: [][]byte{sctBytes}}, nil
}

func TestNewCertificateAuthorityImpl_BadSerialPrefix(t *testing.T) {
	t.Parallel()
	cargs := newCAArgs(t)

	cargs.serialPrefix = 0x00
	_, err := cargs.make()
	if err == nil {
		t.Errorf("NewCertificateAuthorityImpl(serialPrefix=0x00) succeeded, but want error")
	}

	cargs.serialPrefix = 0x80
	_, err = cargs.make()
	if err == nil {
		t.Errorf("NewCertificateAuthorityImpl(serialPrefix=0x80) succeeded, but want error")
	}
}

func TestNewCertificateAuthorityImpl_InsufficientIssuers(t *testing.T) {
	t.Parallel()
	cargs := newCAArgs(t)
	origIssuers := cargs.issuers

	for _, tc := range []struct {
		name    string
		issuers []*issuance.Issuer
		wantErr string
	}{
		{
			name:    "no issuers",
			issuers: nil,
			wantErr: "at least one issuer",
		},
		{
			name:    "ecdsa only",
			issuers: origIssuers[2:],
			wantErr: "no RSA issuers configured",
		},
		{
			name:    "rsa only",
			issuers: origIssuers[:2],
			wantErr: "no ECDSA issuers configured",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			cargs.issuers = tc.issuers
			_, err := cargs.make()
			if err == nil {
				t.Fatalf("NewCertificateAuthorityImpl(%s) succeeded, but want error", tc.name)
			}

			if !strings.Contains(err.Error(), tc.wantErr) {
				t.Fatalf("NewCertificateAuthorityImpl(%s) = %q, but want %q", tc.name, err, tc.wantErr)
			}
		})
	}
}

func TestNewCertificateAuthorityImpl_InsufficientProfiles(t *testing.T) {
	t.Parallel()
	cargs := newCAArgs(t)
	cargs.profiles = nil

	_, err := cargs.make()
	if err == nil {
		t.Fatalf("NewCertificateAuthorityImpl(profiles=nil) succeeded, but want error")
	}

	wantErr := "at least one certificate profile"
	if !strings.Contains(err.Error(), wantErr) {
		t.Fatalf("NewCertificateAuthorityImpl(profiles=nil) = %q, but want %q", err, wantErr)
	}
}

// recordingSA keeps track of the serial, precertificate, and certificate which
// are written to it. We use recordingSA only for the _HappyPath test because
// it's a pain to mitigate the data-races inherent in writing to it from many
// parallel subtests.
type recordingSA struct {
	serial         *sapb.AddSerialRequest
	precertificate *sapb.AddCertificateRequest
	certificate    *sapb.AddCertificateRequest
}

func (m *recordingSA) AddSerial(ctx context.Context, req *sapb.AddSerialRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	m.serial = req
	return &emptypb.Empty{}, nil
}

func (m *recordingSA) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	m.precertificate = req
	return &emptypb.Empty{}, nil
}

func (m *recordingSA) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
	m.certificate = req
	return &emptypb.Empty{}, nil
}

func (m *recordingSA) GetCertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
	return nil, berrors.NotFoundError("cannot find the cert")
}

func (m *recordingSA) GetLintPrecertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
	return nil, berrors.NotFoundError("cannot find the precert")
}

func findExtension(extensions []pkix.Extension, id asn1.ObjectIdentifier) *pkix.Extension {
	for _, ext := range extensions {
		if ext.Id.Equal(id) {
			return &ext
		}
	}
	return nil
}

// deserializeSCTList deserializes a list of SCTs.
// Forked from github.com/cloudflare/cfssl/helpers
func deserializeSCTList(sctListExtensionValue []byte) ([]ct.SignedCertificateTimestamp, error) {
	var serializedSCTList []byte
	_, err := asn1.Unmarshal(sctListExtensionValue, &serializedSCTList)
	if err != nil {
		return nil, err
	}

	var sctList ctx509.SignedCertificateTimestampList
	rest, err := cttls.Unmarshal(serializedSCTList, &sctList)
	if err != nil {
		return nil, err
	}
	if len(rest) != 0 {
		return nil, errors.New("serialized SCT list contained trailing garbage")
	}
	list := make([]ct.SignedCertificateTimestamp, len(sctList.SCTList))
	for i, serializedSCT := range sctList.SCTList {
		var sct ct.SignedCertificateTimestamp
		rest, err := cttls.Unmarshal(serializedSCT.Val, &sct)
		if err != nil {
			return nil, err
		}
		if len(rest) != 0 {
			return nil, errors.New("serialized SCT contained trailing garbage")
		}
		list[i] = sct
	}
	return list, nil
}

func TestIssueCertificate_HappyPath(t *testing.T) {
	for _, profile := range []string{"legacy", "modern"} {
		for _, tc := range []struct {
			name string
			csr  []byte
		}{
			{
				name: "RSA",
				csr:  CNandSANCSR,
			},
			{
				name: "ECDSA",
				csr:  ECDSACSR,
			},
			{
				name: "unrecognized extension",
				csr:  UnsupportedExtensionCSR,
			},
			{
				name: "poison extension",
				csr:  CTPoisonExtensionCSR,
			},
			{
				name: "malformed poison extension",
				csr:  CTPoisonExtensionEmptyCSR,
			},
			{
				// Rejection of CSRs that request Must-Staple happens in the RA.
				name: "must staple extension",
				csr:  mustRead("./testdata/must_staple.der.csr"),
			},
		} {
			t.Run(tc.name+"/"+profile, func(t *testing.T) {
				t.Parallel()

				// Use our own CA for each of these parallel subtests, because we plan
				// to inspect the serial, precert, and final cert stored in the mock SA.
				// Also so we can assert that certain metrics have specific values.
				cargs := newCAArgs(t)
				sa := &recordingSA{}
				cargs.sa = sa
				ca, err := cargs.make()
				if err != nil {
					t.Fatalf("making test ca: %s", err)
				}

				res, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{
					RegistrationID: 1, OrderID: 1,
					Csr: tc.csr, CertProfileName: profile,
				})
				if err != nil {
					t.Fatalf("IssueCertificate(%s) = %q, but want success", tc.name, err)
				}

				test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 1)
				test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "certificate", "status": "success"}, 1)

				if sa.serial.RegID != 1 {
					t.Errorf("want serial to be associated with acct %d, but got %d", 1, sa.serial.RegID)
				}

				storedPrecert, err := x509.ParseCertificate(sa.precertificate.Der)
				if err != nil {
					t.Fatalf("parsing precert: %s", err)
				}

				poisonExtension := findExtension(storedPrecert.Extensions, OIDExtensionCTPoison)
				if poisonExtension == nil {
					t.Fatal("failed to find ctpoison extension")
				}

				if !poisonExtension.Critical {
					t.Error("precertificate ctpoison extension must be critical")
				}

				if !bytes.Equal(poisonExtension.Value, []byte{0x05, 0x00}) { // ASN.1 DER NULL
					t.Errorf("precertificate poison extension has value %x, but want %x", poisonExtension.Value, []byte{0x05, 0x00})
				}

				storedCert, err := x509.ParseCertificate(sa.certificate.Der)
				if err != nil {
					t.Fatalf("parsing cert: %s", err)
				}

				sctExtension := findExtension(storedCert.Extensions, OIDExtensionSCTList)
				if sctExtension == nil {
					t.Fatal("failed to find sctList extension")
				}

				if sctExtension.Critical {
					t.Error("sctList extension must not be critical")
				}

				sctList, err := deserializeSCTList(sctExtension.Value)
				if err != nil {
					t.Fatalf("parsing sctList extension: %s", err)
				}

				if len(sctList) != 1 {
					t.Errorf("got %d SCTs, but want 1", len(sctList))
				}

				cert, err := x509.ParseCertificate(res.DER)
				if err != nil {
					t.Fatalf("parsing returned cert: %s", err)
				}

				if (sa.serial.Serial != core.SerialToString(storedPrecert.SerialNumber)) ||
					(sa.serial.Serial != core.SerialToString(storedCert.SerialNumber)) ||
					(sa.serial.Serial != core.SerialToString(cert.SerialNumber)) {
					t.Errorf("expected all serials to match")
				}

				if !bytes.Equal(res.DER, sa.certificate.Der) {
					t.Errorf("Expected stored and returned cert to be identical")
				}
			})
		}
	}
}

func TestIssueCertificate_BadCSR(t *testing.T) {
	t.Parallel()

	ca, err := newCAArgs(t).make()
	if err != nil {
		t.Fatalf("making test ca: %s", err)
	}

	for _, tc := range []struct {
		name    string
		csrPath string
	}{
		{
			name:    "no names",
			csrPath: "./testdata/no_names.der.csr",
		},
		{
			name:    "too many names",
			csrPath: "./testdata/too_many_names.der.csr",
		},
		{
			name:    "short key",
			csrPath: "./testdata/short_key.der.csr",
		},
		{
			name:    "bad key algorithm",
			csrPath: "./testdata/bad_algorithm.der.csr",
		},
		{
			name:    "invalid signature",
			csrPath: "./testdata/invalid_signature.der.csr",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			_, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{
				RegistrationID: 1, OrderID: 1,
				Csr: mustRead(tc.csrPath), CertProfileName: "legacy",
			})
			if err == nil {
				t.Fatalf("IssueCertificate(%q) succeeded, but want error", tc.csrPath)
			}
			if !errors.Is(err, berrors.BadCSR) {
				t.Fatalf("IssueCertificate(%q) = %T, but want %T", tc.csrPath, err, berrors.BadCSR)
			}
		})
	}
}

func TestIssueCertificate_ValidPastIssuer(t *testing.T) {
	t.Parallel()
	cargs := newCAArgs(t)

	// Limit ourselves to only having one ECDSA issuer, just in case they have
	// different notAfter dates.
	cargs.issuers = cargs.issuers[:3]

	// Jump to a time just moments before the test issuer expire.
	future := cargs.issuers[2].Cert.Certificate.NotAfter.Add(-1 * time.Hour)
	cargs.clk.Set(future)

	ca, err := cargs.make()
	if err != nil {
		t.Fatalf("making test ca: %s", err)
	}

	_, err = ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{
		RegistrationID: 1, OrderID: 1,
		Csr: ECDSACSR, CertProfileName: "legacy",
	})
	if err == nil {
		t.Fatalf("IssueCertificate(notAfter > issuer.notAfter) succeeded, but want error")
	}
	if !errors.Is(err, berrors.InternalServer) {
		t.Fatalf("IssueCertificate(notAfter > issuer.notAfter) = %T, but want %T", err, berrors.InternalServer)
	}
}

func TestIssueCertificate_InvalidProfile(t *testing.T) {
	t.Parallel()

	ca, err := newCAArgs(t).make()
	if err != nil {
		t.Fatalf("making test ca: %s", err)
	}

	for _, tc := range []struct {
		name    string
		profile string
		wantErr string
	}{
		{
			name:    "no profile",
			profile: "",
			wantErr: "Incomplete issue certificate request",
		},
		{
			name:    "unrecognized profile",
			profile: "doesnotexist",
			wantErr: "incapable of using a profile named",
		},
		{
			name:    "invalid profile name",
			profile: "🤓",
			wantErr: "incapable of using a profile named",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			_, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{
				RegistrationID: 1, OrderID: 1,
				Csr: ECDSACSR, CertProfileName: tc.profile,
			})
			if err == nil {
				t.Fatalf("IssueCertificate(profile=%q) succeeded, but want error", tc.profile)
			}
			if !strings.Contains(err.Error(), tc.wantErr) {
				t.Fatalf("IssueCertificate(profile=%q) = %q, but want %q", tc.profile, err, tc.wantErr)
			}
		})
	}
}

func TestIssueCertificate_ProfileSelection(t *testing.T) {
	t.Parallel()

	ca, err := newCAArgs(t).make()
	if err != nil {
		t.Fatalf("making test ca: %s", err)
	}

	for _, tc := range []struct {
		profile      string
		wantValidity time.Duration
	}{
		{
			profile:      "legacy",
			wantValidity: 90 * 24 * time.Hour,
		},
		{
			profile:      "modern",
			wantValidity: 6 * 24 * time.Hour,
		},
	} {
		t.Run(tc.profile, func(t *testing.T) {
			t.Parallel()

			res, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{
				RegistrationID: 1, OrderID: 1,
				Csr: ECDSACSR, CertProfileName: tc.profile,
			})
			if err != nil {
				t.Fatalf("IssueCertificate(profile=%q) = %q, but want success", tc.profile, err)
			}

			cert, err := x509.ParseCertificate(res.DER)
			if err != nil {
				t.Fatalf("parsing certificate: %s", err)
			}

			// We use the validity period as a proxy for detecting whether the correct
			// profile was selected and used, since we know that the validity period
			// differs between the two test profiles.
			validity := cert.NotAfter.Add(time.Second).Sub(cert.NotBefore)
			if validity != tc.wantValidity {
				t.Errorf("IssueCertificate(profile=%q) = validity %d, but want %d", tc.profile, validity, tc.wantValidity)
			}
		})
	}
}

func TestIssueCertificate_IssuerSelection(t *testing.T) {
	t.Parallel()
	cargs := newCAArgs(t)
	origIssuers := cargs.issuers

	ca, err := cargs.make()
	if err != nil {
		t.Fatalf("making test ca: %s", err)
	}

	for _, tc := range []struct {
		name        string
		csr         []byte
		wantIssuers []*issuance.Issuer
		wantKUs     x509.KeyUsage
	}{
		{
			name:        "ECDSA",
			csr:         ECDSACSR,
			wantIssuers: origIssuers[2:],
			wantKUs:     x509.KeyUsageDigitalSignature,
		},
		{
			name:        "RSA",
			csr:         CNandSANCSR,
			wantIssuers: origIssuers[:2],
			wantKUs:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			res, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{
				RegistrationID: 1, OrderID: 1,
				Csr: tc.csr, CertProfileName: "legacy",
			})
			if err != nil {
				t.Fatalf("IssueCertificate(csr=%q) = %q, but want success", tc.name, err)
			}

			cert, err := x509.ParseCertificate(res.DER)
			if err != nil {
				t.Fatalf("parsing certificate: %s", err)
			}

			if cert.KeyUsage != tc.wantKUs {
				t.Errorf("IssueCertificate(csr=%q) has KU %v, but want %v", tc.name, cert.KeyUsage, tc.wantKUs)
			}

			validated := false
			for _, issuer := range tc.wantIssuers {
				err = cert.CheckSignatureFrom(issuer.Cert.Certificate)
				if err == nil {
					validated = true
					break
				}
			}
			if !validated {
				t.Errorf("IssueCertificate(csr=%q) issued from unexpected issuer %q", tc.name, cert.Issuer.CommonName)
			}
		})
	}
}

func TestIssueCertificate_UnpredictableIssuance(t *testing.T) {
	ca, err := newCAArgs(t).make()
	if err != nil {
		t.Fatalf("creating test ca: %s", err)
	}

	// Issue the same (ECDSA-keyed) certificate 20 times. At least one issuance
	// should come from each of the two active ECDSA issuers (int-e1 and int-e2).
	// With 20 trials, the probability that all 20 issuances come from the same
	// issuer is 0.5 ^ 20 = 9.5e-7 ~= 1e-6 = 1 in a million, so we do not consider
	// this test to be flaky.
	seenE1 := false
	seenE2 := false
	for range 20 {
		res, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{
			RegistrationID: 1, OrderID: 1,
			Csr: ECDSACSR, CertProfileName: "legacy",
		})
		if err != nil {
			t.Fatalf("issuing certificate: %s", err)
		}

		cert, err := x509.ParseCertificate(res.DER)
		if err != nil {
			t.Fatalf("parsing certificate: %s", err)
		}

		if strings.Contains(cert.Issuer.CommonName, "E1") {
			seenE1 = true
		} else if strings.Contains(cert.Issuer.CommonName, "E2") {
			seenE2 = true
		} else {
			t.Fatalf("Issued certificate from unexpected issuer")
		}
	}

	if !seenE1 {
		t.Error("Expected at least one issuance from active issuer E1")
	}
	if !seenE2 {
		t.Error("Expected at least one issuance from active issuer E2")
	}
}

// TestPickIssuer tests the various deterministic cases, ensuring that the
// function properly respects the issuers' key algorithms and profiles. The test
// cases here are somewhat tightly coupled to the profiles populated by
// newCAArgs; this full coverage is to ensure that pickIssuer doesn't have an
// off-by-one error or similar bug lurking in it.
//
// The non-deterministic case is covered by TestIssueCertificate_UnpredictableIssuance.
func TestPickIssuer(t *testing.T) {
	t.Parallel()

	ca, err := newCAArgs(t).make()
	if err != nil {
		t.Fatalf("creating test ca: %s", err)
	}

	for _, tc := range []struct {
		name    string
		profile string
		keyAlg  x509.PublicKeyAlgorithm
		wantErr bool
	}{
		{
			name:    "unrecognized profile",
			profile: "doesnotexist",
			keyAlg:  x509.ECDSA,
			wantErr: true,
		},
		{
			name:    "unrecognized key algorithm",
			profile: "modern",
			keyAlg:  x509.Ed25519,
			wantErr: true,
		},
		{
			name:    "recognized/legacy+ecdsa",
			profile: "legacy",
			keyAlg:  x509.ECDSA,
		},
		{
			name:    "recognized/legacy+rsa",
			profile: "legacy",
			keyAlg:  x509.RSA,
		},
		{
			name:    "recognized/modern+ecdsa",
			profile: "modern",
			keyAlg:  x509.ECDSA,
		},
		{
			name:    "recognized/modern+rsa",
			profile: "modern",
			keyAlg:  x509.RSA,
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			_, err := ca.pickIssuer(tc.profile, tc.keyAlg)
			if err == nil && tc.wantErr {
				t.Errorf("pickIssuer(%s, %s) = success, but want error", tc.profile, tc.keyAlg)
			} else if err != nil && !tc.wantErr {
				t.Errorf("pickIssuer(%s, %s) = %s, but want success", tc.profile, tc.keyAlg, err)
			}
		})
	}
}

func TestPickIssuer_Inactive(t *testing.T) {
	t.Parallel()
	cargs := newCAArgs(t)

	// Load our own set of issuers, but with half of them inactive.
	var issuers []*issuance.Issuer
	for i, name := range []string{"int-r3", "int-r4", "int-e1", "int-e2"} {
		issuer, err := issuance.LoadIssuer(issuance.IssuerConfig{
			Active:     i%2 == 0,
			IssuerURL:  fmt.Sprintf("http://not-example.com/i/%s", name),
			CRLURLBase: fmt.Sprintf("http://not-example.com/c/%s/", name),
			CRLShards:  10,
			Location: issuance.IssuerLoc{
				File:     fmt.Sprintf("../test/hierarchy/%s.key.pem", name),
				CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name),
			},
			Profiles: []string{"legacy", "modern"},
		}, cargs.clk)
		if err != nil {
			t.Fatalf("loading test issuer: %s", err)
		}
		issuers = append(issuers, issuer)
	}
	cargs.issuers = issuers

	ca, err := cargs.make()
	if err != nil {
		t.Fatalf("creating test ca: %s", err)
	}

	// Calling pickIssuer should never return one of the inactive issuers.
	for range 20 {
		issuer, err := ca.pickIssuer("modern", x509.ECDSA)
		if err != nil {
			t.Fatalf("pickIssuer(modern, ECDSA) = %s, but want success", err)
		}
		if strings.Contains(issuer.Name(), "E2") {
			t.Errorf("pickIssuer(modern, ECDSA) = E2, but only want E1")
		}
	}
	for range 20 {
		issuer, err := ca.pickIssuer("modern", x509.RSA)
		if err != nil {
			t.Fatalf("pickIssuer(modern, RSA) = %s, but want success", err)
		}
		if strings.Contains(issuer.Name(), "R4") {
			t.Errorf("pickIssuer(modern, RSA) = R4, but only want R3")
		}
	}
}

func TestNoteSignError(t *testing.T) {
	testCtx := newCAArgs(t)
	metrics := testCtx.metrics

	err := fmt.Errorf("wrapped non-signing error: %w", errors.New("oops"))
	metrics.noteSignError(err)
	test.AssertMetricWithLabelsEquals(t, metrics.signErrorCount, prometheus.Labels{"type": "HSM"}, 0)

	err = fmt.Errorf("wrapped signing error: %w", pkcs11.Error(5))
	metrics.noteSignError(err)
	test.AssertMetricWithLabelsEquals(t, metrics.signErrorCount, prometheus.Labels{"type": "HSM"}, 1)
}

func TestGenerateSKID(t *testing.T) {
	t.Parallel()
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "Error generating key")

	sha256skid, err := generateSKID(key.Public())
	test.AssertNotError(t, err, "Error generating SKID")
	test.AssertEquals(t, len(sha256skid), 20)
	test.AssertEquals(t, cap(sha256skid), 20)
	features.Reset()
}

func TestVerifyTBSCertIsDeterministic(t *testing.T) {
	t.Parallel()

	// Create first keypair and cert
	testKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "unable to generate ECDSA private key")
	template := &x509.Certificate{
		NotAfter:     time.Now().Add(1 * time.Hour),
		DNSNames:     []string{"example.com"},
		SerialNumber: big.NewInt(1),
	}
	certDer1, err := x509.CreateCertificate(rand.Reader, template, template, &testKey.PublicKey, testKey)
	test.AssertNotError(t, err, "unable to create certificate")

	// Create second keypair and cert
	testKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	test.AssertNotError(t, err, "unable to generate ECDSA private key")
	template2 := &x509.Certificate{
		NotAfter:     time.Now().Add(2 * time.Hour),
		DNSNames:     []string{"example.net"},
		SerialNumber: big.NewInt(2),
	}
	certDer2, err := x509.CreateCertificate(rand.Reader, template2, template2, &testKey2.PublicKey, testKey2)
	test.AssertNotError(t, err, "unable to create certificate")

	testCases := []struct {
		name          string
		lintCertBytes []byte
		leafCertBytes []byte
		errorSubstr   string
	}{
		{
			name:          "Both nil",
			lintCertBytes: nil,
			leafCertBytes: nil,
			errorSubstr:   "were nil",
		},
		{
			name:          "Missing a value, invalid input",
			lintCertBytes: nil,
			leafCertBytes: []byte{0x6, 0x6, 0x6},
			errorSubstr:   "were nil",
		},
		{
			name:          "Missing a value, valid input",
			lintCertBytes: nil,
			leafCertBytes: certDer1,
			errorSubstr:   "were nil",
		},
		{
			name:          "Mismatched bytes, invalid input",
			lintCertBytes: []byte{0x6, 0x6, 0x6},
			leafCertBytes: []byte{0x1, 0x2, 0x3},
			errorSubstr:   "malformed certificate",
		},
		{
			name:          "Mismatched bytes, invalider input",
			lintCertBytes: certDer1,
			leafCertBytes: []byte{0x1, 0x2, 0x3},
			errorSubstr:   "malformed certificate",
		},
		{
			// This case is an example of when a linting cert's DER bytes are
			// mismatched compared to then precert or final cert created from
			// that linting cert's DER bytes.
			name:          "Mismatched bytes, valid input",
			lintCertBytes: certDer1,
			leafCertBytes: certDer2,
			errorSubstr:   "mismatch between",
		},
		{
			// Take this with a grain of salt since this test is not actually
			// creating a linting certificate and performing two
			// x509.CreateCertificate() calls like
			// ca.IssueCertificateForPrecertificate and
			// ca.issuePrecertificateInner do. However, we're still going to
			// verify the equality.
			name:          "Valid",
			lintCertBytes: certDer1,
			leafCertBytes: certDer1,
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			err := tbsCertIsDeterministic(testCase.lintCertBytes, testCase.leafCertBytes)
			if testCase.errorSubstr != "" {
				test.AssertError(t, err, "your lack of errors is disturbing")
				test.AssertContains(t, err.Error(), testCase.errorSubstr)
			} else {
				test.AssertNotError(t, err, "unexpected error")
			}
		})
	}
}
